feat: add MSSQL/Fabric support to data-parity skill#705
feat: add MSSQL/Fabric support to data-parity skill#705suryaiyer95 wants to merge 15 commits intomainfrom
Conversation
There was a problem hiding this comment.
Claude Code Review
This repository is configured for manual code reviews. Comment @claude review to trigger a review and subscribe this PR to future pushes, or @claude review once for a one-time review.
Tip: disable this comment in your organization's Code Review settings.
📝 WalkthroughWalkthroughThis PR adds comprehensive support for Microsoft Fabric as a warehouse connection type. Key changes include updating the SQL Server driver to support Azure AD authentication flows using Changes
Sequence Diagram(s)sequenceDiagram
participant App as Application
participant Driver as SQL Server Driver
participant AzureId as `@azure/identity`
participant AzureAD as Azure AD
participant CLI as CLI (az command)
participant MsSQL as mssql Library
App->>Driver: connect(config with Azure AD auth)
Driver->>Driver: Normalize auth type<br/>(e.g., "service-principal" → "azure-active-directory-service-principal-secret")
alt `@azure/identity` Available
Driver->>AzureId: DefaultAzureCredential<br/>.getToken()
AzureId->>AzureAD: Request access token
AzureAD-->>AzureId: Return access token
AzureId-->>Driver: Token acquired
else `@azure/identity` Fails
Driver->>CLI: execSync("az account<br/>get-access-token")
CLI->>AzureAD: Request token via CLI
AzureAD-->>CLI: Return token
CLI-->>Driver: Token string
end
Driver->>MsSQL: new ConnectionPool(config<br/>with auth + token)
MsSQL->>AzureAD: Authenticate with token
AzureAD-->>MsSQL: Connection established
MsSQL-->>Driver: Pool connected
Driver-->>App: Connected driver instance
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 1 | ❌ 2❌ Failed checks (2 warnings)
✅ Passed checks (1 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
5 issues found across 30 files
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="packages/opencode/src/altimate/native/connections/data-diff.ts">
<violation number="1" location="packages/opencode/src/altimate/native/connections/data-diff.ts:266">
P2: Missing single-quote escaping on `partitionValue` in the date-mode branches. The categorical mode (6 lines above) escapes with `.replace(/'/g, "''")`, but none of the date-mode `switch` cases do. Apply the same escaping for consistency and defense in depth.</violation>
<violation number="2" location="packages/opencode/src/altimate/native/connections/data-diff.ts:489">
P2: `partitionColumn` is not identifier-quoted in `buildPartitionDiscoverySQL`, but it is quoted in `buildPartitionWhereClause`. If the column name is a reserved word (e.g. `date`, `order`), the discovery query will produce a syntax error. Quote the column consistently between both functions.</violation>
</file>
<file name="packages/opencode/src/altimate/native/connections/registry.ts">
<violation number="1" location="packages/opencode/src/altimate/native/connections/registry.ts:125">
P2: `fabric` is missing from the `PASSWORD_DRIVERS` set. Since it maps to the same sqlserver driver as `sqlserver`/`mssql` (both present in the set), `fabric` should also be included to get the same friendly error when `password` is not a string.</violation>
</file>
<file name="packages/opencode/src/altimate/tools/data-diff.ts">
<violation number="1" location="packages/opencode/src/altimate/tools/data-diff.ts:206">
P2: When `d.values` is nullish, `d.values?.join(" | ")` evaluates to `undefined`, which the template literal coerces to the string `"undefined"`. The output would read e.g. `[source only] undefined`. Use a fallback to produce a sensible message.</violation>
</file>
<file name="packages/drivers/src/sqlserver.ts">
<violation number="1" location="packages/drivers/src/sqlserver.ts:148">
P1: `flattenRow` flattens all array values, but only the empty-string key (`""`) holds mssql's merged unnamed columns. A legitimate array column value (e.g. from JSON aggregation) will be incorrectly spread, corrupting the row data and misaligning columns. Restrict flattening to the `""` key only.</violation>
</file>
Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review, or fix all with cubic.
403477e to
811c2be
Compare
|
@suryaiyer95 can you address code review comments? |
There was a problem hiding this comment.
1 issue found across 2 files (changes from recent commits).
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="packages/drivers/src/sqlserver.ts">
<violation number="1" location="packages/drivers/src/sqlserver.ts:90">
P2: Avoid `execSync` in the async connection path; it can block the event loop for up to the CLI timeout.</violation>
</file>
Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review, or fix all with cubic.
There was a problem hiding this comment.
1 issue found across 2 files (changes from recent commits).
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="packages/opencode/src/altimate/native/connections/data-diff.ts">
<violation number="1" location="packages/opencode/src/altimate/native/connections/data-diff.ts:819">
P2: `crossWarehouse` is computed from unresolved params, so an omitted `source_warehouse` can be misclassified as cross-warehouse when both sides actually run on the same default warehouse.</violation>
</file>
Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review, or fix all with cubic.
Bun's runtime never fires native addon async callbacks, so the async `new duckdb.Database(path, opts, callback)` form would hit the 2-second timeout fallback on every connection attempt. Switch to the synchronous constructor form `new duckdb.Database(path)` / `new duckdb.Database(path, opts)` which throws on error and completes immediately in both Node and bun runtimes. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The async callback form with 2s fallback was already working correctly at e3df5a4. The timeout was caused by a missing duckdb .node binary, not a bun incompatibility. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add `warehouseTypeToDialect()` mapping: sqlserver→tsql, mssql→tsql, fabric→fabric, postgresql→postgres, mariadb→mysql. Fixes critical serde mismatch where Rust engine rejects raw warehouse type names. - Update both `resolveDialect()` functions to use the mapping - Add MSSQL/Fabric cases to `dateTruncExpr()` — DATETRUNC(DAY, col) - Add locale-safe date literal casting via CONVERT(DATE, ..., 23) - Register `fabric` in DRIVER_MAP (reuses sqlserver TDS driver) - Add `fabric` normalize aliases in normalize.ts - Add 15 SQL Server driver unit tests (TOP injection, truncation, schema introspection, connection lifecycle, result format) - Add 9 dialect mapping unit tests Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Support all 7 Azure AD / Entra ID auth types in `sqlserver.ts`: `azure-active-directory-password`, `access-token`, `service-principal-secret`, `msi-vm`, `msi-app-service`, `azure-active-directory-default`, `token-credential` - Force TLS encryption for all Azure AD connections - Dynamic import of `@azure/identity` for `DefaultAzureCredential` - Add normalize aliases for Azure AD config fields (`authentication`, `azure_tenant_id`, `azure_client_id`, `azure_client_secret`, `access_token`) - Add `fabric: SQLSERVER_ALIASES` to DRIVER_ALIASES - Add 10 Azure AD unit tests covering all auth flows, encryption, and `DefaultAzureCredential` with managed identity Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…LL.md - Add SQL Server / Fabric schema inspection query in Step 2 - Add "SQL Server and Microsoft Fabric" section with: - Supported configurations table (sqlserver, mssql, fabric) - Fabric connection guide with Azure AD auth types - Algorithm behavior notes (joindiff vs hashdiff selection) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…rscore column filter
- **Azure AD auth**: Pass `azure-active-directory-*` types directly to tedious
instead of constructing `DefaultAzureCredential` ourselves. Tedious imports
`@azure/identity` internally and creates credentials — avoids bun CJS/ESM
`isTokenCredential` boundary issue that caused "not an instance of the token
credential class" errors.
- **Auth shorthands**: Map `CLI`, `default`, `password`, `service-principal`,
`msi`, `managed-identity` to their full tedious type names.
- **Column filter**: Remove `_.startsWith("_")` filter from `execute()` result
columns — it stripped legitimate aliases like `_p` used by partition discovery,
causing partitioned diffs to return empty results.
- **Tests**: Remove `@azure/identity` mock (no longer imported by driver),
update auth assertions, add shorthand mapping tests, fix column filter test.
- **Verified**: All 97 driver tests pass. Full data-diff pipeline tested against
real MSSQL server (profile, joindiff, auto, where_clause, partitioned).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…lattening - Upgrade `mssql` from v11 to v12 (`tedious` 18 → 19) - Use explicit `ConnectionPool` instead of global `mssql.connect()` to isolate multiple simultaneous connections - Flatten unnamed column arrays — `mssql` merges unnamed columns (e.g. `SELECT COUNT(*), SUM(...)`) into a single array under the empty-string key; restore positional column values - Proper column name resolution: compare `namedKeys.length` against flattened row length, fall back to synthetic `col_0`, `col_1`, etc. - Update test mock to export `ConnectionPool` class and `createMockPool` Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…tions
Use ternary expressions (`x ? {...} : {}`) instead of short-circuit
(`x && {...}`) to avoid spreading a boolean value.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- P1: restrict `flattenRow` to only spread the empty-string key (`""`) where mssql merges unnamed columns, preserving legitimate array values - P2: escape single quotes in `partitionValue` for date-mode branches in `buildPartitionWhereClause` (categorical mode already escaped) - P2: add `fabric` to `PASSWORD_DRIVERS` set in registry for consistent password validation alongside `sqlserver`/`mssql` - P2: fallback to `"(no values)"` when `d.values` is nullish to prevent template literal coercing `undefined` to the string `"undefined"` Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- sqlserver-unit: 3 tests for unnamed column flattening — verifies only the empty-string key is spread, legitimate named arrays are preserved - driver-normalize: fabric type uses SQLSERVER_ALIASES (server → host, trustServerCertificate → trust_server_certificate) - connections: fabric type is recognized in DRIVER_MAP and listed correctly Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add "Minimum Version Requirements" table to SKILL.md covering SQL Server 2022+, mssql v12, and @azure/identity v4 with rationale for each - Document auth shorthands (CLI, default, password, service-principal, msi) - Move @azure/identity from dependencies to optional peerDependencies so it is NOT installed by default — only required for Azure AD auth - Add runtime check in sqlserver driver: if Azure AD auth type is requested but @azure/identity is missing, throw a clear install instruction error Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…solution - For `azure-active-directory-default` (CLI/default auth), acquire token ourselves instead of delegating to tedious's internal `@azure/identity` - Strategy: try `DefaultAzureCredential` first, fall back to `az` CLI subprocess - Bypasses Bun resolving `@azure/identity` to browser bundle where `DefaultAzureCredential` is a non-functional stub - Also bypasses CJS/ESM `isTokenCredential` boundary mismatch - All 31 driver unit tests pass, verified against real Fabric endpoint Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…oken` when none supplied
The `azure-active-directory-access-token` branch passed
`token: config.token ?? config.access_token` to tedious. When neither
field was set on a connection (e.g. a `fabric-migration` entry that
declared the auth type but no token), tedious threw:
TypeError: The "config.authentication.options.token" property
must be of type string
This blocked any Fabric/MSSQL config that relied on ambient credentials
(Azure CLI / managed identity) but used the explicit
`azure-active-directory-access-token` type instead of the `default`
shorthand.
Refactor token acquisition (`DefaultAzureCredential` → `az` CLI
fallback) into a shared `acquireAzureToken()` helper used by both the
`default` path and the `access-token` path when no token was supplied.
Callers that pass an explicit token are unchanged.
Also harden `mock.module("node:child_process", ...)` in
`sqlserver-unit.test.ts` to spread the real module so sibling tests in
the same `bun test` run keep access to `spawn` / `exec` / `fork`.
Tests: 110 pass, 0 fail in `packages/drivers`.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…ry mode When `source` and `target` are both SQL queries, `resolveTableSources` wraps them as `__diff_source` / `__diff_target` CTEs and the executor prepends the combined `WITH …` block to every engine-emitted task. T-SQL and Fabric parse-bind every CTE body even when unreferenced, so a task routed to the source warehouse failed to resolve the target-only base table referenced inside the unused `__diff_target` CTE (and vice versa), producing `Invalid object name` errors from the wrong warehouse. Return side-specific prefixes from `resolveTableSources` alongside the combined one, and have the executor loop in `runDataDiff` pick the source or target prefix per task when `source_warehouse !== target_warehouse`. Same-warehouse behaviour is unchanged. Adds `data-diff-cte.test.ts` covering plain-name passthrough, both-query wrapping, side-specific CTE isolation, and CTE merging with engine-emitted `WITH` clauses (10 tests). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Commit 333a45c moved `@azure/identity` from `optionalDependencies` to `peerDependencies` with `optional: true` in `packages/drivers/package.json`, but the lockfile was not regenerated. That left CI under `--frozen-lockfile` broken and made fresh installs silently diverge from the committed state. Running `bun install` brings the lockfile in sync: `@azure/identity` is recorded as an optional peer, and its transitive pins (`@azure/msal-browser`, `@azure/msal-common`, `@azure/msal-node`) re-resolve to the versions required by `tedious` and `snowflake-sdk`, matching the reachable runtime surface. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
52fe09e to
1977232
Compare
There was a problem hiding this comment.
Actionable comments posted: 9
🧹 Nitpick comments (4)
packages/opencode/src/altimate/native/connections/data-diff.ts (1)
815-820: Cross-warehouse detection is a purely textual compare.
sourceWarehouse !== targetWarehousetreatsundefinedvs an explicit name as "cross-warehouse" even when the explicit name is the default (first) warehouse. The side-specific CTE injection is a strict superset of the combined prefix, so correctness isn't affected — just a minor inefficiency (slightly narrower CTE sent). Not blocking.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/opencode/src/altimate/native/connections/data-diff.ts` around lines 815 - 820, Summary: crossWarehouse currently does a raw textual compare (sourceWarehouse !== targetWarehouse) which can misclassify undefined vs an explicit default warehouse name as cross-warehouse. Fix: canonicalize both sides before comparing by resolving undefined/empty values to the effective/default warehouse name used elsewhere (use the same resolver used for connection/effective settings) instead of comparing raw params.source_warehouse and params.target_warehouse; update the variables referenced (sourceWarehouse, targetWarehouse, crossWarehouse) so crossWarehouse = resolvedSourceWarehouse !== resolvedTargetWarehouse. Ensure you call the existing resolution helper (or add one) rather than inventing a new default string.packages/drivers/test/sqlserver-unit.test.ts (1)
483-489: Assertion gap lets the "mixed named + unnamed" column-naming bug slip through.This test verifies
rowsfor{ name: "alice", "": [42] }but doesn't assertresult.columns. Under the current implementation (sqlserver.ts Lines 210-217), mixing a named column with multiple unnamed values (e.g.{ name: "alice", "": [42, 100] }) collapses the header to["col_0", "col_1", "col_2"]and dropsname. Adding a case with ≥ 2 unnamed values and assertingcolumnswould have caught that and will guard whichever fix lands.♻️ Suggested additional case
test("handles mix of named and unnamed columns", async () => { mockQueryResult = { recordset: [{ name: "alice", "": [42] }], } const result = await connector.execute("SELECT * FROM t") expect(result.rows).toEqual([["alice", 42]]) }) + + test("preserves named-column header when mixed with multiple unnamed columns", async () => { + // SELECT name, COUNT(*), SUM(x) FROM t → { name: "alice", "": [42, 100] } + mockQueryResult = { + recordset: [{ name: "alice", "": [42, 100] }], + } + const result = await connector.execute("SELECT name, COUNT(*), SUM(x) FROM t") + expect(result.rows).toEqual([["alice", 42, 100]]) + expect(result.columns[0]).toBe("name") + })🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/drivers/test/sqlserver-unit.test.ts` around lines 483 - 489, Add a new test variant in packages/drivers/test/sqlserver-unit.test.ts that uses a recordset like { name: "alice", "": [42, 100] } (i.e., multiple unnamed values) and assert both result.rows and result.columns so the header preserves the named column "name" and includes generated column names for the unnamed values (e.g., ["name","col_1","col_2"]); this will surface the bug referenced around sqlserver.ts (logic near lines handling column mapping) and prevent regressions in connector.execute's column assembly.packages/opencode/test/altimate/data-diff-dialect.test.ts (1)
51-54: Consider expanding case-insensitivity coverage.The case-insensitivity block only exercises
sqlserverandpostgresql. Sincemssql,fabric, andmariadbgo through the same normalization path, a one-line additional assertion (e.g.,MSSQL→tsql,FABRIC→fabric) would guard against a future regression where only some branches get lowercased.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/opencode/test/altimate/data-diff-dialect.test.ts` around lines 51 - 54, Add assertions to the "handles uppercase input" test to cover other inputs that share the normalization path so case-insensitivity can't regress: update the test block in packages/opencode/test/altimate/data-diff-dialect.test.ts that calls warehouseTypeToDialect to also assert uppercase variants such as "MSSQL" -> "tsql", "FABRIC" -> "fabric" and "MARIADB" -> "mariadb" (or whichever expected dialects map in warehouseTypeToDialect) so all branches that are lowercased are exercised.packages/drivers/src/sqlserver.ts (1)
89-100:execSyncblocks the event loop duringconnect().
execSync("az account get-access-token ...")with a 15 s timeout runs synchronously on the main thread inside anasyncfunction — any other pending I/O on the runtime is stalled while Azure CLI spins up (typically 0.5–2 s, occasionally much longer). Prefer the asyncexecFile(orpromisify(exec)) so the token fetch cooperates with the event loop.♻️ Suggested change
- try { - const { execSync } = await import("node:child_process") - const out = execSync( - "az account get-access-token --resource https://database.windows.net/ --query accessToken -o tsv", - { encoding: "utf-8", timeout: 15000, stdio: ["pipe", "pipe", "pipe"] }, - ).trim() - if (out) token = out - } catch { - // az CLI not installed or not logged in - } + try { + const { execFile } = await import("node:child_process") + const { promisify } = await import("node:util") + const run = promisify(execFile) + const { stdout } = await run( + "az", + ["account", "get-access-token", "--resource", "https://database.windows.net/", "--query", "accessToken", "-o", "tsv"], + { encoding: "utf-8", timeout: 15000 }, + ) + const out = stdout.trim() + if (out) token = out + } catch { + // az CLI not installed or not logged in + }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/drivers/src/sqlserver.ts` around lines 89 - 100, The current token fetch inside connect() uses sync child process invocation (execSync) which blocks the event loop; replace it with an asynchronous call (e.g., use child_process.execFile or promisified exec via util.promisify) so the Azure CLI call runs without blocking. Locate the block that imports execSync and sets token (the token variable and the try/catch that calls "az account get-access-token ...") and change it to call the async API (await the execFile/promisified exec) with the same args and timeout, handle stdout.trim() to set token, and preserve the existing catch behavior for missing CLI or auth failures.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In @.opencode/skills/data-parity/SKILL.md:
- Around line 453-458: The fenced code block showing the pseudo-YAML config (the
block containing type: "fabric", host:
"<workspace-id>-<item-id>.datawarehouse.fabric.microsoft.com", database:
"<warehouse-name>", authentication: "azure-active-directory-default") is missing
a language tag; change the opening fence from ``` to ```yaml (or ```text) so
markdownlint MD040 is satisfied and the block is treated as YAML.
In `@packages/drivers/package.json`:
- Around line 20-33: Remove the redundant optional peer dependency entry for
"@azure/identity" from package.json: delete the "@azure/identity" key inside the
"peerDependencies" object and its corresponding entry in "peerDependenciesMeta"
since "mssql"/"tedious" already bundles it; update the package.json so only the
other dependencies (e.g., "mssql", "oracledb", "duckdb", "mongodb",
"@clickhouse/client") remain and no "@azure/identity" references exist under
"peerDependencies" or "peerDependenciesMeta".
In `@packages/drivers/src/sqlserver.ts`:
- Around line 53-54: The code calls rawAuth.toLowerCase() without ensuring
rawAuth is a string, causing a TypeError for non-string authentication values;
update the computation of authType in the sqlserver driver to first check typeof
rawAuth === "string" before using toLowerCase and looking up AUTH_SHORTHANDS,
e.g. only apply AUTH_SHORTHANDS[rawAuth.toLowerCase()] when rawAuth is a string,
otherwise pass rawAuth through (or leave undefined) so non-string auth blocks
(config.authentication) don't blow up at runtime; adjust the logic around
rawAuth, authType, and AUTH_SHORTHANDS to be type-safe and explicit.
- Around line 56-158: The code path that handles authType starting with
"azure-active-directory" (see authType variable and mssqlConfig.authentication
assignments) does not handle unknown subtypes and can leave mssqlConfig without
authentication; add a terminal else branch after the existing
azure-active-directory-* branches that throws a clear Error (e.g. mentioning the
unsupported authType) so unknown or misspelled Azure AD subtypes fail fast;
ensure the thrown error references authType so callers get an explicit message
instead of a later opaque tedious error.
- Around line 76-87: The empty catch around the `@azure/identity` import and
credential.getToken is hiding real authentication errors; change the catch to
capture the thrown error (e.g., catch (err) { azureIdentityErr = err }) and
preserve that error in a local variable, then either append its message/details
to the final thrown error or log it at debug level when token remains undefined;
update the block that currently throws the generic "install `@azure/identity` or
run az login" message to include the captured azureIdentityErr (or its
message/stack) so real AAD/authentication failures are visible while still
tolerating missing-module/browser-bundle cases.
- Around line 208-217: The current columns logic replaces known named headers
when sampleFlat is longer (mixed named + unnamed aggregates); change the
construction in the columns branch to preserve the existing named prefix
(namedKeys) and only synthesize column names for the extra flattened entries
(e.g., generate col_i for indices >= namedKeys.length up to
sampleFlat.length-1), falling back to result.recordset.columns only when both
namedKeys and sampleFlat are empty; update the conditional around
sampleFlat/namedKeys where columns is assigned (the expression that uses
sampleFlat, namedKeys, recordset, and result.recordset.columns) so known headers
are retained and only the tail is renamed.
- Around line 162-167: Remove the fallback to the global shared pool to avoid
reintroducing cross-database isolation; in the block that currently checks
MssqlConnectionPool, ensure that if MssqlConnectionPool is falsy you throw an
explicit error instead of calling mssql.connect, i.e., keep the branch that
constructs and connects a new MssqlConnectionPool(mssqlConfig) and replace the
else branch with a throw that references MssqlConnectionPool and mssql.connect
in the message so reviewers can quickly locate and understand the change.
In `@packages/drivers/test/sqlserver-unit.test.ts`:
- Around line 154-159: The test "empty result returns correctly" uses a fake
mssql response shape by setting mockQueryResult.recordset_columns, but the
driver reads result.recordset?.columns; update the test to match real mssql
shape by removing recordset_columns and instead set mockQueryResult.recordset =
[] and, if you need to include column metadata, set
mockQueryResult.recordset.columns = {} (or leave absent for empty results) so
the driver code that checks result.recordset?.columns and the code paths in
connector.execute behave against the real response shape.
---
Nitpick comments:
In `@packages/drivers/src/sqlserver.ts`:
- Around line 89-100: The current token fetch inside connect() uses sync child
process invocation (execSync) which blocks the event loop; replace it with an
asynchronous call (e.g., use child_process.execFile or promisified exec via
util.promisify) so the Azure CLI call runs without blocking. Locate the block
that imports execSync and sets token (the token variable and the try/catch that
calls "az account get-access-token ...") and change it to call the async API
(await the execFile/promisified exec) with the same args and timeout, handle
stdout.trim() to set token, and preserve the existing catch behavior for missing
CLI or auth failures.
In `@packages/drivers/test/sqlserver-unit.test.ts`:
- Around line 483-489: Add a new test variant in
packages/drivers/test/sqlserver-unit.test.ts that uses a recordset like { name:
"alice", "": [42, 100] } (i.e., multiple unnamed values) and assert both
result.rows and result.columns so the header preserves the named column "name"
and includes generated column names for the unnamed values (e.g.,
["name","col_1","col_2"]); this will surface the bug referenced around
sqlserver.ts (logic near lines handling column mapping) and prevent regressions
in connector.execute's column assembly.
In `@packages/opencode/src/altimate/native/connections/data-diff.ts`:
- Around line 815-820: Summary: crossWarehouse currently does a raw textual
compare (sourceWarehouse !== targetWarehouse) which can misclassify undefined vs
an explicit default warehouse name as cross-warehouse. Fix: canonicalize both
sides before comparing by resolving undefined/empty values to the
effective/default warehouse name used elsewhere (use the same resolver used for
connection/effective settings) instead of comparing raw params.source_warehouse
and params.target_warehouse; update the variables referenced (sourceWarehouse,
targetWarehouse, crossWarehouse) so crossWarehouse = resolvedSourceWarehouse !==
resolvedTargetWarehouse. Ensure you call the existing resolution helper (or add
one) rather than inventing a new default string.
In `@packages/opencode/test/altimate/data-diff-dialect.test.ts`:
- Around line 51-54: Add assertions to the "handles uppercase input" test to
cover other inputs that share the normalization path so case-insensitivity can't
regress: update the test block in
packages/opencode/test/altimate/data-diff-dialect.test.ts that calls
warehouseTypeToDialect to also assert uppercase variants such as "MSSQL" ->
"tsql", "FABRIC" -> "fabric" and "MARIADB" -> "mariadb" (or whichever expected
dialects map in warehouseTypeToDialect) so all branches that are lowercased are
exercised.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro
Run ID: 6589a3e0-5c1a-4ac6-bd8a-e405334413ca
⛔ Files ignored due to path filters (1)
bun.lockis excluded by!**/*.lock
📒 Files selected for processing (12)
.opencode/skills/data-parity/SKILL.mdpackages/drivers/package.jsonpackages/drivers/src/normalize.tspackages/drivers/src/sqlserver.tspackages/drivers/test/sqlserver-unit.test.tspackages/opencode/src/altimate/native/connections/data-diff.tspackages/opencode/src/altimate/native/connections/registry.tspackages/opencode/src/altimate/tools/data-diff.tspackages/opencode/test/altimate/connections.test.tspackages/opencode/test/altimate/data-diff-cte.test.tspackages/opencode/test/altimate/data-diff-dialect.test.tspackages/opencode/test/altimate/driver-normalize.test.ts
| ``` | ||
| type: "fabric" | ||
| host: "<workspace-id>-<item-id>.datawarehouse.fabric.microsoft.com" | ||
| database: "<warehouse-name>" | ||
| authentication: "azure-active-directory-default" # recommended | ||
| ``` |
There was a problem hiding this comment.
Add yaml (or text) language to the fenced block.
markdownlint MD040 flags missing language. Since the block is a pseudo-YAML config example, yaml reads best:
💚 Fix
-```
+```yaml
type: "fabric"
host: "<workspace-id>-<item-id>.datawarehouse.fabric.microsoft.com"
database: "<warehouse-name>"
authentication: "azure-active-directory-default" # recommended</details>
<!-- suggestion_start -->
<details>
<summary>📝 Committable suggestion</summary>
> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
```suggestion
🧰 Tools
🪛 markdownlint-cli2 (0.22.0)
[warning] 453-453: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In @.opencode/skills/data-parity/SKILL.md around lines 453 - 458, The fenced
code block showing the pseudo-YAML config (the block containing type: "fabric",
host: "<workspace-id>-<item-id>.datawarehouse.fabric.microsoft.com", database:
"<warehouse-name>", authentication: "azure-active-directory-default") is missing
a language tag; change the opening fence from ``` to ```yaml (or ```text) so
markdownlint MD040 is satisfied and the block is treated as YAML.
| "mssql": "^12.0.0", | ||
| "oracledb": "^6.0.0", | ||
| "duckdb": "^1.0.0", | ||
| "mongodb": "^6.0.0", | ||
| "@clickhouse/client": "^1.0.0" | ||
| }, | ||
| "peerDependencies": { | ||
| "@azure/identity": ">=4.0.0" | ||
| }, | ||
| "peerDependenciesMeta": { | ||
| "@azure/identity": { | ||
| "optional": true | ||
| } | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🌐 Web query:
Does mssql 12.x on npm bundle or peer-depend on @azure/identity, and what is the current minimum @azure/identity version for its azure-active-directory-* auth types?
💡 Result:
No, the mssql 12.x npm package does not bundle or peer-depend on @azure/identity. It depends on tedious ^19.0.0, which bundles @azure/identity ^4.2.1 as a regular dependency. For azure-active-directory-* auth types (such as azure-active-directory-default, azure-active-directory-password, azure-active-directory-access-token, azure-active-directory-msi-vm, azure-active-directory-msi-app-service, azure-active-directory-service-principal-secret), tedious handles the authentication internally using its bundled @azure/identity, so no separate installation or specific minimum version of @azure/identity is required by the user. The effective minimum version is whatever tedious 19.x requires, which is ^4.2.1.
Citations:
- 1: https://registry.npmjs.org/mssql
- 2: https://www.npmjs.com/package/mssql?activeTab=versions
- 3: https://www.npmjs.com/package/mssql
- 4: feat: Migrate to azure identity tediousjs/tedious#1328
- 5: https://github.com/tediousjs/tedious
- 6: fix: update azure identity version tediousjs/tedious#1635
- 7: https://registry.npmjs.org/tedious
- 8: https://www.npmjs.com/package/tedious
- 9: http://tediousjs.github.io/tedious/api-connection.html
Remove @azure/identity from peerDependencies — it's redundant and already bundled by tedious.
mssql v12 depends on tedious ^19.0.0, which bundles @azure/identity ^4.2.1 as a regular dependency. Adding @azure/identity as an optional peer dependency in drivers/package.json is unnecessary; tedious handles Azure AD authentication internally without user intervention. Users installing mssql automatically get the correct @azure/identity version through the tedious dependency chain.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@packages/drivers/package.json` around lines 20 - 33, Remove the redundant
optional peer dependency entry for "@azure/identity" from package.json: delete
the "@azure/identity" key inside the "peerDependencies" object and its
corresponding entry in "peerDependenciesMeta" since "mssql"/"tedious" already
bundles it; update the package.json so only the other dependencies (e.g.,
"mssql", "oracledb", "duckdb", "mongodb", "@clickhouse/client") remain and no
"@azure/identity" references exist under "peerDependencies" or
"peerDependenciesMeta".
| const rawAuth = config.authentication as string | undefined | ||
| const authType = rawAuth ? (AUTH_SHORTHANDS[rawAuth.toLowerCase()] ?? rawAuth) : undefined |
There was a problem hiding this comment.
Non-string authentication will throw on toLowerCase().
config.authentication is typed as unknown at the call site and is cast to string | undefined, but there's no runtime check. A caller passing a non-string (e.g. a config object, a null, or a pre-built { type, options } authentication block — which some users might try) will hit TypeError: rawAuth.toLowerCase is not a function instead of a meaningful driver error. A typeof rawAuth === "string" guard would keep the shorthand path strictly opt-in.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@packages/drivers/src/sqlserver.ts` around lines 53 - 54, The code calls
rawAuth.toLowerCase() without ensuring rawAuth is a string, causing a TypeError
for non-string authentication values; update the computation of authType in the
sqlserver driver to first check typeof rawAuth === "string" before using
toLowerCase and looking up AUTH_SHORTHANDS, e.g. only apply
AUTH_SHORTHANDS[rawAuth.toLowerCase()] when rawAuth is a string, otherwise pass
rawAuth through (or leave undefined) so non-string auth blocks
(config.authentication) don't blow up at runtime; adjust the logic around
rawAuth, authType, and AUTH_SHORTHANDS to be type-safe and explicit.
| if (authType?.startsWith("azure-active-directory")) { | ||
| ;(mssqlConfig.options as any).encrypt = true | ||
|
|
||
| // Resolve a raw Azure AD access token. | ||
| // Used by both `azure-active-directory-default` and by | ||
| // `azure-active-directory-access-token` when no token was provided. | ||
| // | ||
| // We acquire the token ourselves rather than letting tedious do it because: | ||
| // 1. Bun can resolve @azure/identity to the browser bundle (inside | ||
| // tedious or even our own import), where DefaultAzureCredential | ||
| // is a non-functional stub that throws. | ||
| // 2. Passing a credential object via type:"token-credential" hits a | ||
| // CJS/ESM isTokenCredential boundary mismatch in Bun. | ||
| // | ||
| // Strategy: try @azure/identity first (works when module resolution | ||
| // is correct), fall back to shelling out to `az account get-access-token` | ||
| // (works everywhere Azure CLI is installed). | ||
| const acquireAzureToken = async (): Promise<string> => { | ||
| let token: string | undefined | ||
|
|
||
| try { | ||
| const azureIdentity = await import("@azure/identity") | ||
| const credential = new azureIdentity.DefaultAzureCredential( | ||
| config.azure_client_id | ||
| ? { managedIdentityClientId: config.azure_client_id as string } | ||
| : undefined, | ||
| ) | ||
| const tokenResponse = await credential.getToken("https://database.windows.net/.default") | ||
| token = tokenResponse?.token | ||
| } catch { | ||
| // @azure/identity unavailable or browser bundle — fall through | ||
| } | ||
|
|
||
| if (!token) { | ||
| try { | ||
| const { execSync } = await import("node:child_process") | ||
| const out = execSync( | ||
| "az account get-access-token --resource https://database.windows.net/ --query accessToken -o tsv", | ||
| { encoding: "utf-8", timeout: 15000, stdio: ["pipe", "pipe", "pipe"] }, | ||
| ).trim() | ||
| if (out) token = out | ||
| } catch { | ||
| // az CLI not installed or not logged in | ||
| } | ||
| } | ||
|
|
||
| if (!token) { | ||
| throw new Error( | ||
| "Azure AD token acquisition failed. Either install @azure/identity (npm install @azure/identity) " + | ||
| "or log in with Azure CLI (az login).", | ||
| ) | ||
| } | ||
| return token | ||
| } | ||
|
|
||
| if (authType === "azure-active-directory-default") { | ||
| mssqlConfig.authentication = { | ||
| type: "azure-active-directory-access-token", | ||
| options: { token: await acquireAzureToken() }, | ||
| } | ||
| } else if (authType === "azure-active-directory-password") { | ||
| mssqlConfig.authentication = { | ||
| type: "azure-active-directory-password", | ||
| options: { | ||
| userName: config.user, | ||
| password: config.password, | ||
| clientId: config.azure_client_id, | ||
| tenantId: config.azure_tenant_id, | ||
| }, | ||
| } | ||
| } else if (authType === "azure-active-directory-access-token") { | ||
| // If the caller supplied a token, use it; otherwise acquire one | ||
| // automatically (DefaultAzureCredential → az CLI). | ||
| const suppliedToken = (config.token ?? config.access_token) as string | undefined | ||
| mssqlConfig.authentication = { | ||
| type: "azure-active-directory-access-token", | ||
| options: { token: suppliedToken ?? (await acquireAzureToken()) }, | ||
| } | ||
| } else if ( | ||
| authType === "azure-active-directory-msi-vm" || | ||
| authType === "azure-active-directory-msi-app-service" | ||
| ) { | ||
| mssqlConfig.authentication = { | ||
| type: authType, | ||
| options: { | ||
| ...(config.azure_client_id ? { clientId: config.azure_client_id } : {}), | ||
| }, | ||
| } | ||
| } else if (authType === "azure-active-directory-service-principal-secret") { | ||
| mssqlConfig.authentication = { | ||
| type: "azure-active-directory-service-principal-secret", | ||
| options: { | ||
| clientId: config.azure_client_id, | ||
| clientSecret: config.azure_client_secret, | ||
| tenantId: config.azure_tenant_id, | ||
| }, | ||
| } | ||
| } | ||
| } else { | ||
| // Standard SQL Server user/password | ||
| mssqlConfig.user = config.user | ||
| mssqlConfig.password = config.password | ||
| } |
There was a problem hiding this comment.
Unknown azure-active-directory-* subtype silently produces an auth-less config.
The chain at Lines 111-153 handles five specific Azure AD subtypes, but any other value starting with azure-active-directory- (e.g. a typo, or a future tedious subtype) skips all branches. Because the else at Line 154 is gated on the outer startsWith check, neither authentication nor user/password is set, and the pool connects with an incomplete config — producing an opaque tedious error far from the root cause. A terminal else { throw new Error(\Unsupported Azure AD auth type: ${authType}`) }inside the outerif` would fail fast.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@packages/drivers/src/sqlserver.ts` around lines 56 - 158, The code path that
handles authType starting with "azure-active-directory" (see authType variable
and mssqlConfig.authentication assignments) does not handle unknown subtypes and
can leave mssqlConfig without authentication; add a terminal else branch after
the existing azure-active-directory-* branches that throws a clear Error (e.g.
mentioning the unsupported authType) so unknown or misspelled Azure AD subtypes
fail fast; ensure the thrown error references authType so callers get an
explicit message instead of a later opaque tedious error.
| try { | ||
| const azureIdentity = await import("@azure/identity") | ||
| const credential = new azureIdentity.DefaultAzureCredential( | ||
| config.azure_client_id | ||
| ? { managedIdentityClientId: config.azure_client_id as string } | ||
| : undefined, | ||
| ) | ||
| const tokenResponse = await credential.getToken("https://database.windows.net/.default") | ||
| token = tokenResponse?.token | ||
| } catch { | ||
| // @azure/identity unavailable or browser bundle — fall through | ||
| } |
There was a problem hiding this comment.
Swallowing every @azure/identity error makes failed logins hard to diagnose.
The bare catch {} at Line 85 is intended to tolerate "module missing" / "browser bundle" cases, but it also hides genuine authentication failures (expired cached creds, AADSTS* errors, network/proxy issues). When both paths fail, the user gets the generic "install @azure/identity or run az login" message with no hint that identity was reachable and rejected them. Consider capturing the error and including it in the final thrown message (or logging at debug level) so the real cause surfaces.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@packages/drivers/src/sqlserver.ts` around lines 76 - 87, The empty catch
around the `@azure/identity` import and credential.getToken is hiding real
authentication errors; change the catch to capture the thrown error (e.g., catch
(err) { azureIdentityErr = err }) and preserve that error in a local variable,
then either append its message/details to the final thrown error or log it at
debug level when token remains undefined; update the block that currently throws
the generic "install `@azure/identity` or run az login" message to include the
captured azureIdentityErr (or its message/stack) so real AAD/authentication
failures are visible while still tolerating missing-module/browser-bundle cases.
| if (MssqlConnectionPool) { | ||
| pool = new MssqlConnectionPool(mssqlConfig) | ||
| await pool.connect() | ||
| } else { | ||
| pool = await mssql.connect(mssqlConfig) | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🌐 Web query:
mssql npm package v12 ConnectionPool named export availability
💡 Result:
Yes, the mssql npm package version 12 provides ConnectionPool as a named export. It can be imported using ES6 named import syntax: import { ConnectionPool } from 'mssql'; or accessed as sql.ConnectionPool after default import: const sql = require('mssql'); new sql.ConnectionPool(config);
Citations:
- 1: https://www.npmjs.com/package/mssql
- 2: https://www.npmjs.com/package/mssql?activeTab=versions
- 3: https://registry.npmjs.org/mssql
- 4: https://github.com/tediousjs/node-mssql
- 5: https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/types/mssql/index.d.ts
- 6: https://stackoverflow.com/questions/49267785/types-mssql-cannot-find-connection-pool-type-defs-even-though-ts-is-looking-i
- 7: https://linkedin.com/company/npm-holding-wll
🏁 Script executed:
# First, let's locate and examine the sqlserver.ts file
find . -name "sqlserver.ts" -type fRepository: AltimateAI/altimate-code
Length of output: 103
🏁 Script executed:
# Check the specific lines mentioned in the review (162-167) with surrounding context
sed -n '150,175p' packages/drivers/src/sqlserver.tsRepository: AltimateAI/altimate-code
Length of output: 955
🏁 Script executed:
# Let's see the imports at the top of the file to understand where MssqlConnectionPool comes from
head -50 packages/drivers/src/sqlserver.tsRepository: AltimateAI/altimate-code
Length of output: 1808
🏁 Script executed:
# Search for the function/method containing these lines to understand the full context
sed -n '140,180p' packages/drivers/src/sqlserver.tsRepository: AltimateAI/altimate-code
Length of output: 1540
🏁 Script executed:
# Check the package.json for mssql version
cat packages/drivers/package.json | grep -A 5 -B 5 "mssql"Repository: AltimateAI/altimate-code
Length of output: 354
🏁 Script executed:
# Search for any comments or context about the fallback or isolation bug
rg "ConnectionPool" packages/drivers/src/sqlserver.ts -B 3 -A 3Repository: AltimateAI/altimate-code
Length of output: 980
🏁 Script executed:
# Look for any tests related to this fallback
find packages/drivers -name "*.test.*" -o -name "*.spec.*" | xargs grep -l "mssql\|ConnectionPool" 2>/dev/nullRepository: AltimateAI/altimate-code
Length of output: 112
🏁 Script executed:
# Check git log for recent changes to sqlserver.ts
git log --oneline -20 packages/drivers/src/sqlserver.ts 2>/dev/null || echo "Git history not available"Repository: AltimateAI/altimate-code
Length of output: 147
🏁 Script executed:
# Let's examine the test file to understand expected behavior
cat packages/drivers/test/sqlserver-unit.test.tsRepository: AltimateAI/altimate-code
Length of output: 16662
🏁 Script executed:
# Check git history with more detail to understand context of the change
git log --all --oneline -30 | grep -i "mssql\|connection\|isolation" || git log --all --oneline -30 packages/drivers/src/Repository: AltimateAI/altimate-code
Length of output: 148
🏁 Script executed:
# Look for any issues or comments in code about the fallback scenario
rg "fallback|global.*pool" packages/drivers/src/sqlserver.ts -iRepository: AltimateAI/altimate-code
Length of output: 50
🏁 Script executed:
# Search the entire drivers package for any documentation about this isolation issue
find packages/drivers -type f \( -name "*.md" -o -name "*.txt" \) | xargs grep -l "isolation\|global.*pool" 2>/dev/null || echo "No docs found"Repository: AltimateAI/altimate-code
Length of output: 81
🏁 Script executed:
# Let's check for any PR or issue references in recent commits
git log --all --grep="isolation\|pool\|ConnectionPool" --oneline 2>/dev/null | head -20 || echo "No matching commits"Repository: AltimateAI/altimate-code
Length of output: 1333
🏁 Script executed:
# Search for any comments about the specific bug being fixed
rg "bug|issue|fix" packages/drivers/src/sqlserver.ts -i -B 2 -A 2Repository: AltimateAI/altimate-code
Length of output: 50
🏁 Script executed:
# Look for mssql documentation references or comments about connect() vs ConnectionPool
rg "connect\(\)|mssql\." packages/drivers/src/sqlserver.ts -B 1 -A 1Repository: AltimateAI/altimate-code
Length of output: 607
🏁 Script executed:
# Check if there are any integration tests that might show the different behaviors
find packages/drivers/test -name "*.test.*" -o -name "*.integration.*" | head -5Repository: AltimateAI/altimate-code
Length of output: 204
🏁 Script executed:
# Check if there are any comments or documentation about why this fallback exists
git log -p --all -- packages/drivers/src/sqlserver.ts | grep -A 10 -B 10 "fallback\|else.*mssql.connect" | head -50 || echo "No relevant history found"Repository: AltimateAI/altimate-code
Length of output: 50
🏁 Script executed:
# Look for any version-specific handling or compatibility notes
rg "version|@12|mssql" packages/drivers/src/sqlserver.ts -iRepository: AltimateAI/altimate-code
Length of output: 1155
🏁 Script executed:
# Check the mssql types to understand the difference between named export and default
find . -path "*/node_modules/mssql*" -name "*.d.ts" -o -name "package.json" | grep mssql | head -5Repository: AltimateAI/altimate-code
Length of output: 50
🏁 Script executed:
# Look at how other drivers handle optional peer dependencies and fallbacks
ls -la packages/drivers/src/ | grep -E "\.ts$"Repository: AltimateAI/altimate-code
Length of output: 988
🏁 Script executed:
# Search for any recent PR or commit that added this fallback logic or introduced the isolation fix
git log --all --oneline -50 packages/drivers/src/sqlserver.tsRepository: AltimateAI/altimate-code
Length of output: 639
🏁 Script executed:
# Check the full context of the connect function to see if there's any explanation
sed -n '1,100p' packages/drivers/src/sqlserver.tsRepository: AltimateAI/altimate-code
Length of output: 4186
🏁 Script executed:
# Check the actual PR that introduced these changes - look for when the drivers were rewritten
git show 1b3a4ab --stat | grep sqlserverRepository: AltimateAI/altimate-code
Length of output: 386
🏁 Script executed:
# Let's see if there's any documentation about why the defensive fallback was added
git show 1b3a4ab:packages/drivers/src/sqlserver.ts 2>/dev/null | head -100 || echo "File may not exist in that commit"Repository: AltimateAI/altimate-code
Length of output: 2907
🏁 Script executed:
# Let's check when the ConnectionPool logic was added - search between the commit and now
git log --oneline --all -- packages/drivers/src/sqlserver.ts | head -20Repository: AltimateAI/altimate-code
Length of output: 639
🏁 Script executed:
# Check the diff between the original and current to see when ConnectionPool was introduced
git show 1b3a4ab:packages/drivers/src/sqlserver.ts 2>/dev/null | grep -i "connectionpool" || echo "Not in original commit"Repository: AltimateAI/altimate-code
Length of output: 90
🏁 Script executed:
# Find the commit that introduced ConnectionPool logic
git log -p --all -- packages/drivers/src/sqlserver.ts | grep -B 5 "MssqlConnectionPool = mod.ConnectionPool" | head -20Repository: AltimateAI/altimate-code
Length of output: 50
🏁 Script executed:
# Search for when ConnectionPool was first introduced in this file
git log --all -p -- packages/drivers/src/sqlserver.ts | grep -A 3 -B 3 "ConnectionPool" | head -40Repository: AltimateAI/altimate-code
Length of output: 1003
🏁 Script executed:
# Let's check what changed between 1b3a4ab and current HEAD for sqlserver.ts
git diff 1b3a4ab HEAD -- packages/drivers/src/sqlserver.ts | head -100Repository: AltimateAI/altimate-code
Length of output: 4780
🏁 Script executed:
# Find the specific commit that introduced ConnectionPool
git log --all --oneline --reverse -- packages/drivers/src/sqlserver.ts | tail -10Repository: AltimateAI/altimate-code
Length of output: 639
🏁 Script executed:
# Find the exact commit that introduced ConnectionPool logic
git log --all -p -- packages/drivers/src/sqlserver.ts | grep -B 20 "MssqlConnectionPool = mod.ConnectionPool" | head -50Repository: AltimateAI/altimate-code
Length of output: 879
🏁 Script executed:
# Check commit c438bb0 to see if it introduced ConnectionPool
git show c438bb0:packages/drivers/src/sqlserver.ts 2>/dev/null | grep -A 10 -B 2 "ConnectionPool" || echo "Not in c438bb0"Repository: AltimateAI/altimate-code
Length of output: 82
🏁 Script executed:
# Check the commits between 1b3a4ab and current
git log --oneline 1b3a4ab..HEAD -- packages/drivers/src/sqlserver.tsRepository: AltimateAI/altimate-code
Length of output: 147
🏁 Script executed:
# Check the specific commit 1977232 to see what changed with ConnectionPool
git show 1977232 -- packages/drivers/src/sqlserver.ts | grep -A 15 -B 5 "ConnectionPool"Repository: AltimateAI/altimate-code
Length of output: 1847
🏁 Script executed:
# Get the full context of this commit
git show --stat 1977232 | head -30Repository: AltimateAI/altimate-code
Length of output: 1738
🏁 Script executed:
# Check if there's a PR associated with this commit
git show 1977232 | head -50Repository: AltimateAI/altimate-code
Length of output: 2529
🏁 Script executed:
# Let's find when ConnectionPool was actually first added
for commit in b907943 9aa1d39 53c38a8 b5242fa; do
echo "=== Checking commit $commit ==="
git show $commit:packages/drivers/src/sqlserver.ts 2>/dev/null | grep -c "ConnectionPool" && echo "Found ConnectionPool" || echo "No ConnectionPool"
doneRepository: AltimateAI/altimate-code
Length of output: 275
🏁 Script executed:
# Let's check the current state and understand the flow better
# Look at how mssql.connect() is documented vs ConnectionPool
web_search
mssql npm tedious package global pool default vs ConnectionPool instance isolationRepository: AltimateAI/altimate-code
Length of output: 162
🌐 Web query:
mssql npm tedious package global pool default vs ConnectionPool instance isolation
💡 Result:
In the context of MSSQL connections using the 'tedious' npm package (the default driver for the 'mssql' package), there is no built-in "global pool default" in tedious itself. Tedious provides individual Connection objects, and connection pooling is handled at a higher level. The 'mssql' package (which uses tedious internally) provides ConnectionPool instances. Each new ConnectionPool(config).connect creates a separate, isolated pool of tedious Connections. These pools do not share connections with each other; each manages its own pool of TDS (tedious) connections. There is a "global pool" accessible via mssql.connect(config), which is a convenience that returns the same pool instance for repeated calls with the same config (in newer versions), but only one global pool exists at a time. Using new ConnectionPool instances bypasses this and gives full isolation. A separate 'tedious-connection-pool' npm package exists (unmaintained since 2020), which provides its own ConnectionPool class wrapping tedious Connections. Each instance of this is also isolated. Best practice: Use a single app-wide ConnectionPool instance (often as a module-exported singleton via Node's module caching) for efficiency, or manage multiple isolated pools (e.g., for different databases) via a custom pool manager. Multiple pools do not share connections, ensuring isolation but consuming more resources if overused. Example of isolated pools in mssql: const pool1 = new sql.ConnectionPool(config1).connect; const pool2 = new sql.ConnectionPool(config2).connect; // pool1 and pool2 manage separate sets of tedious connections
Citations:
- 1: https://tediousjs.github.io/node-mssql/
- 2: https://github.com/tediousjs/node-mssql/
- 3: https://tediousjs.github.io/node-mssql
- 4: https://github.com/tediousjs/node-mssql/blob/master/lib/tedious/connection-pool.js
- 5: https://stackoverflow.com/questions/64254145/node-js-mssql-multiple-concurrent-connections-to-sql-servers-interfering-with-ea
- 6: Multiple connections in the same Node.js process tediousjs/node-mssql#1376
- 7: Multiple ConnectionPools? tediousjs/node-mssql#753
Drop the fallback else branch or throw when ConnectionPool is unavailable.
The fallback at line 165–166 to mssql.connect(mssqlConfig) uses a global shared pool, which defeats the per-database isolation that this change introduces. Since the code requires mssql@^12.0.0 where ConnectionPool is a stable named export, the fallback is unlikely to trigger; if it does, it silently reintroduces the isolation bug. Either throw an error or remove the else branch entirely.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@packages/drivers/src/sqlserver.ts` around lines 162 - 167, Remove the
fallback to the global shared pool to avoid reintroducing cross-database
isolation; in the block that currently checks MssqlConnectionPool, ensure that
if MssqlConnectionPool is falsy you throw an explicit error instead of calling
mssql.connect, i.e., keep the branch that constructs and connects a new
MssqlConnectionPool(mssqlConfig) and replace the else branch with a throw that
references MssqlConnectionPool and mssql.connect in the message so reviewers can
quickly locate and understand the change.
| const sampleFlat = rows.length > 0 ? rows[0] : [] | ||
| const namedKeys = recordset.length > 0 ? Object.keys(recordset[0]) : [] | ||
| const columns = | ||
| rows.length > 0 | ||
| ? Object.keys(rows[0]).filter((k) => !k.startsWith("_")) | ||
| : (result.recordset?.columns | ||
| ? Object.keys(result.recordset.columns) | ||
| : []) | ||
| const truncated = effectiveLimit > 0 && rows.length > effectiveLimit | ||
| const limitedRows = truncated ? rows.slice(0, effectiveLimit) : rows | ||
| namedKeys.length === sampleFlat.length | ||
| ? namedKeys | ||
| : sampleFlat.length > 0 | ||
| ? sampleFlat.map((_: any, i: number) => `col_${i}`) | ||
| : (result.recordset?.columns | ||
| ? Object.keys(result.recordset.columns) | ||
| : []) |
There was a problem hiding this comment.
Mixed named + multi-unnamed columns lose their named headers.
When a row has both named columns and multiple unnamed aggregates (e.g. SELECT name, COUNT(*), SUM(x) FROM t → { name: "alice", "": [42, 100] }), namedKeys.length === 2 but sampleFlat.length === 3, so the fallback replaces every column with col_0..col_N — including the name column whose header was known. Consider preserving the known prefix and only synthesizing names for the flattened tail.
♻️ Suggested approach
- const rows = limitedRecordset.map(flattenRow)
- const sampleFlat = rows.length > 0 ? rows[0] : []
- const namedKeys = recordset.length > 0 ? Object.keys(recordset[0]) : []
- const columns =
- namedKeys.length === sampleFlat.length
- ? namedKeys
- : sampleFlat.length > 0
- ? sampleFlat.map((_: any, i: number) => `col_${i}`)
- : (result.recordset?.columns
- ? Object.keys(result.recordset.columns)
- : [])
+ const rows = limitedRecordset.map(flattenRow)
+ const sampleFlat = rows.length > 0 ? rows[0] : []
+ const sampleRow = recordset[0] ?? {}
+ const columns: string[] = []
+ if (sampleFlat.length === 0) {
+ if (result.recordset?.columns) columns.push(...Object.keys(result.recordset.columns))
+ } else {
+ let i = 0
+ for (const [k, v] of Object.entries(sampleRow)) {
+ if (k === "" && Array.isArray(v)) {
+ for (let j = 0; j < v.length; j++) columns.push(`col_${i++}`)
+ } else {
+ columns.push(k === "" ? `col_${i}` : k)
+ i++
+ }
+ }
+ }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@packages/drivers/src/sqlserver.ts` around lines 208 - 217, The current
columns logic replaces known named headers when sampleFlat is longer (mixed
named + unnamed aggregates); change the construction in the columns branch to
preserve the existing named prefix (namedKeys) and only synthesize column names
for the extra flattened entries (e.g., generate col_i for indices >=
namedKeys.length up to sampleFlat.length-1), falling back to
result.recordset.columns only when both namedKeys and sampleFlat are empty;
update the conditional around sampleFlat/namedKeys where columns is assigned
(the expression that uses sampleFlat, namedKeys, recordset, and
result.recordset.columns) so known headers are retained and only the tail is
renamed.
| test("empty result returns correctly", async () => { | ||
| mockQueryResult = { recordset: [], recordset_columns: {} } | ||
| const result = await connector.execute("SELECT * FROM t") | ||
| expect(result.rows).toEqual([]) | ||
| expect(result.truncated).toBe(false) | ||
| }) |
There was a problem hiding this comment.
recordset_columns isn't a real mssql response shape.
mssql exposes column metadata at result.recordset.columns, not as a sibling recordset_columns key. The driver code (Lines 215-216) looks up result.recordset?.columns, so the key set here has no effect — the test still passes only because recordset is empty. Either drop the field or rename it to mirror the real shape, otherwise future readers will copy the wrong pattern.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@packages/drivers/test/sqlserver-unit.test.ts` around lines 154 - 159, The
test "empty result returns correctly" uses a fake mssql response shape by
setting mockQueryResult.recordset_columns, but the driver reads
result.recordset?.columns; update the test to match real mssql shape by removing
recordset_columns and instead set mockQueryResult.recordset = [] and, if you
need to include column metadata, set mockQueryResult.recordset.columns = {} (or
leave absent for empty results) so the driver code that checks
result.recordset?.columns and the code paths in connector.execute behave against
the real response shape.
| case "sqlserver": | ||
| case "mssql": | ||
| case "tsql": | ||
| case "fabric": | ||
| // Style 23 = ISO-8601 (yyyy-mm-dd), locale-safe | ||
| return `${expr} = CONVERT(DATE, '${escaped}', 23)` |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Check how sqlserver driver serializes Date values in row output.
fd -t f 'sqlserver.ts' packages/drivers/src | xargs rg -n -C4 -P '(Date|toISO|useUTC|dateStrings|TYPES|parseJSON|rowsAsArray)'Repository: AltimateAI/altimate-code
Length of output: 52
🏁 Script executed:
find packages -name "sqlserver.ts" -o -name "*sqlserver*" -type f | head -20Repository: AltimateAI/altimate-code
Length of output: 146
🏁 Script executed:
# Find the data-diff file first
find packages -name "data-diff.ts" -type fRepository: AltimateAI/altimate-code
Length of output: 180
🏁 Script executed:
# Explore the drivers directory structure
ls -la packages/drivers/src/ 2>/dev/null || ls -la packages/*/src/ | head -30Repository: AltimateAI/altimate-code
Length of output: 1103
🏁 Script executed:
cat -n packages/drivers/src/sqlserver.tsRepository: AltimateAI/altimate-code
Length of output: 12990
🏁 Script executed:
sed -n '160,180p' packages/opencode/src/altimate/native/connections/data-diff.tsRepository: AltimateAI/altimate-code
Length of output: 926
🏁 Script executed:
sed -n '580,610p' packages/opencode/src/altimate/native/connections/data-diff.tsRepository: AltimateAI/altimate-code
Length of output: 975
🏁 Script executed:
grep -r "mssql" packages/drivers/package.json packages/*/package.json 2>/dev/null | grep -v node_modulesRepository: AltimateAI/altimate-code
Length of output: 175
🏁 Script executed:
# Find buildPartitionDiscoverySQL function
grep -n "buildPartitionDiscoverySQL\|DATETRUNC" packages/opencode/src/altimate/native/connections/data-diff.ts | head -20Repository: AltimateAI/altimate-code
Length of output: 374
🏁 Script executed:
# Look for the full buildPartitionDiscoverySQL function
sed -n '430,480p' packages/opencode/src/altimate/native/connections/data-diff.tsRepository: AltimateAI/altimate-code
Length of output: 1687
🏁 Script executed:
sed -n '531,570p' packages/opencode/src/altimate/native/connections/data-diff.tsRepository: AltimateAI/altimate-code
Length of output: 1307
🏁 Script executed:
# Check mssql driver documentation for date handling options
grep -n "useUTC\|dateStrings\|parseJSON\|number" packages/drivers/src/sqlserver.tsRepository: AltimateAI/altimate-code
Length of output: 256
🏁 Script executed:
# Verify DATETRUNC return type behavior and check if there's any post-query normalization
sed -n '690,730p' packages/opencode/src/altimate/native/connections/data-diff.tsRepository: AltimateAI/altimate-code
Length of output: 1569
🏁 Script executed:
# Check what the mssql driver returns for DATETRUNC and verify String(Date) behavior
cat > /tmp/test_date.js << 'EOF'
const d = new Date('2024-01-01');
console.log("Date object:", d);
console.log("String(date):", String(d));
console.log("date.toISOString().slice(0, 10):", d.toISOString().slice(0, 10));
EOF
node /tmp/test_date.jsRepository: AltimateAI/altimate-code
Length of output: 226
🏁 Script executed:
# Verify mssql driver v12 documentation by looking at driver setup in sqlserver.ts
# Check if there are any driver options that would normalize dates to strings
grep -n "dateAsString\|useUTC\|legacyTypes" packages/drivers/src/sqlserver.tsRepository: AltimateAI/altimate-code
Length of output: 50
🏁 Script executed:
# Look for any normalization in the normalize.ts file
grep -n "Date\|ISO\|dateStrings" packages/drivers/src/normalize.ts | head -20Repository: AltimateAI/altimate-code
Length of output: 50
🏁 Script executed:
# Verify if there's a test that would catch this MSSQL date partition issue
grep -n "CONVERT\|buildPartitionWhereClause\|DATETRUNC" packages/opencode/src/altimate/native/connections/data-diff.ts | grep -A2 -B2 "595\|600"Repository: AltimateAI/altimate-code
Length of output: 281
🏁 Script executed:
# Check if there are any tests for buildPartitionWhereClause with MSSQL
find packages -name "*.test.ts" -o -name "*.spec.ts" | xargs grep -l "buildPartitionWhereClause\|data-diff" 2>/dev/null | head -5Repository: AltimateAI/altimate-code
Length of output: 179
🏁 Script executed:
# Check test files to see if MSSQL partition tests exist
cat packages/opencode/test/altimate/data-diff-dialect.test.ts | head -100Repository: AltimateAI/altimate-code
Length of output: 1762
🏁 Script executed:
# Search for MSSQL/SQL Server specific tests in data-diff tests
grep -n "sqlserver\|mssql\|tsql\|fabric\|CONVERT" packages/opencode/test/altimate/data-diff-dialect.test.tsRepository: AltimateAI/altimate-code
Length of output: 526
🏁 Script executed:
# Check if there are any integration tests or mssql-specific tests
grep -r "fabric\|mssql\|sqlserver\|CONVERT.*DATE" packages/opencode/test/ --include="*.test.ts" --include="*.spec.ts"Repository: AltimateAI/altimate-code
Length of output: 4251
🏁 Script executed:
# Verify the exact behavior by checking if there's any config or option to handle date parsing
grep -B5 -A5 "options.*encrypt\|options.*trustServerCertificate" packages/drivers/src/sqlserver.tsRepository: AltimateAI/altimate-code
Length of output: 553
🏁 Script executed:
# Final verification: check if there's any Date-to-ISO normalization elsewhere in the executeQuery path
grep -B10 -A10 "return result.rows.map" packages/opencode/src/altimate/native/connections/data-diff.tsRepository: AltimateAI/altimate-code
Length of output: 867
Fix MSSQL/Fabric date partition literal format.
When buildPartitionDiscoverySQL executes for MSSQL/Fabric with date granularity, it returns DATETRUNC(MONTH, [col]) which the mssql v12 driver converts to a JS Date object. In executeQuery, this is stringified via String(v), producing "Mon Jan 01 2024 00:00:00 GMT+0000 (UTC)".
However, buildPartitionWhereClause (line 595–600) passes this string directly into CONVERT(DATE, '${escaped}', 23). Style 23 expects ISO format (yyyy-mm-dd), so the query will fail at parse/convert time: Conversion failed when converting date ... from character string.
Normalize the literal to ISO format before use:
Suggested fix
// date mode
const expr = dateTruncExpr(granularity!, quotedCol, dialect)
- const escaped = partitionValue.replace(/'/g, "''")
+ // Normalize JS Date/ISO timestamps to plain yyyy-mm-dd for dialect-safe casting
+ const isoDate = (() => {
+ const d = new Date(partitionValue)
+ return isNaN(d.getTime())
+ ? partitionValue
+ : d.toISOString().slice(0, 10)
+ })()
+ const escaped = isoDate.replace(/'/g, "''")
Summary
connect/execute/listSchemas/listTables/describeTable/closewith T-SQLTOPinjection,sys.*catalog queries, and row flattening for unnamed columnsdefault,password,access-token,service-principal-secret,msi-vm,msi-app-service) with shorthand aliases (cli,default,password,service-principal,msi)sqlserver/mssql→tsql,fabric→fabricwithDATETRUNC()andCONVERT(DATE, ..., 23)for date partitioningConnectionPoolisolation for concurrent connections, unnamed-column array flattening, synthetic column name fallbackKey files
packages/drivers/src/sqlserver.ts,packages/drivers/test/sqlserver-unit.test.tspackages/opencode/src/altimate/native/connections/data-diff.tspackages/opencode/src/altimate/tools/data-diff.ts.opencode/skills/data-parity/SKILL.mdTest plan
bun test packages/drivers/test/sqlserver-unit.test.ts— 28 tests passbun test packages/opencode/test/altimate/data-diff-dialect.test.ts— 9 tests pass🤖 Generated with Claude Code
Summary by cubic
Adds full MSSQL and Microsoft Fabric support to the data-parity skill with Azure AD auth, T‑SQL/Fabric dialect mapping, and an upgraded
mssqlv12 driver. Also fixes cross‑warehouse SQL‑query mode by injecting side‑specific CTEs to avoid T‑SQL/Fabric parse errors.New Features
auto,joindiff,hashdiff,profile,cascade), date partitioning viaDATETRUNC()andCONVERT(DATE, ..., 23), robust SQL‑vs‑table detection (keyword + whitespace), and dialect mappingsqlserver/mssql→tsql,fabric→fabric,postgresql→postgres,mariadb→mysql.default/CLI,password,service-principal,msi); TLS forced for AAD; token auto‑acquisition fordefault/CLIand whenazure-active-directory-access-tokenis set without a token; explicitConnectionPoolisolation; T‑SQLTOPinjection withExecuteOptions.noLimit; stable result shaping for unnamed columns and synthetic names when needed.fabricto reuse the SQL Server driver; normalize aliases forauthenticationand Azure fields; upgrademssqlto v12; make@azure/identityan optional peer dependency; SKILL docs add minimum versions and a Fabric connection guide; lockfile regenerated.Bug Fixes
noLimitbypasses injectedTOP; stricter SQL‑vs‑table detection.CONVERT(DATE, ..., 23); show “(no values)” when diff rows have no values; addfabricto password-driver validation.Written for commit 1977232. Summary will update on new commits.
Summary by CodeRabbit
New Features
Bug Fixes
Tests